use anyhow::Result;
use bytes::Bytes;
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
use reqwest::{Client, ClientBuilder, IntoUrl, Method};
use std::collections::hash_map::RandomState;
use std::collections::HashMap;
use std::error::Error;
use std::str::FromStr;

lazy_static! {
    pub(self) static ref HTTP_CLIENT: Client = ClientBuilder::default().build().unwrap();
}

pub struct HttpResponse {
    pub status: u16,
    pub headers: HashMap<String, String>,
    pub data: Bytes,
}

impl HttpResponse {
    pub fn to_str_data(&self) -> String {
        String::from_utf8_lossy(self.data.as_ref()).to_string()
    }
    pub fn to_str_data_with(&self, encoding: &str) -> String {
        String::from_utf8_lossy(self.data.as_ref()).to_string()
    }
    pub fn as_data(&self) -> &[u8] {
        self.data.as_ref()
    }
    pub fn has_data(&self) -> bool {
        self.data.len() == 0
    }
    pub fn is_ok(&self) -> bool {
        self.status == 200
    }

    ///  status < 300
    pub fn is_normal_status(&self) -> bool {
        self.status < 300
    }
}
//

pub async fn get(url: &str) -> Result<HttpResponse> {
    get_with_headers(url, &HashMap::new()).await
}

pub async fn get_with_headers(
    url: &str,
    headers: &HashMap<String, String>,
) -> Result<HttpResponse> {
    do_request(url, "GET", None, headers).await
}

pub async fn post(url: &str, bytes: Option<Bytes>) -> Result<HttpResponse> {
    post_with_headers(url, bytes, &HashMap::new()).await
}

pub async fn post_with_headers(
    url: &str,
    bytes: Option<Bytes>,
    headers: &HashMap<String, String>,
) -> Result<HttpResponse> {
    do_request(url, "POST", bytes, headers).await
}

async fn do_request(
    url: &str,
    method: &str,
    data: Option<Bytes>,
    headers: &HashMap<String, String>,
) -> Result<HttpResponse> {
    let data = data.unwrap_or(Bytes::new());
    let client: &Client = &HTTP_CLIENT;
    let mut h = HeaderMap::new();
    headers.iter().for_each(|kv| {
        let header_name = HeaderName::from_str(kv.0).unwrap_or(HeaderName::from_static(""));
        h.insert(
            header_name,
            HeaderValue::from_str(kv.1).unwrap_or(HeaderValue::from_static("")),
        );
    });

    let body = match method {
        "GET" => client.request(Method::GET, url).headers(h).send().await?,
        "POST" => {
            client
                .request(Method::POST, url)
                .headers(h)
                .body(data)
                .send()
                .await?
        }
        _ => client.request(Method::GET, url).headers(h).send().await?,
    };
    let mut resp_headers = HashMap::new();
    body.headers().iter().for_each(|kv| {
        resp_headers.insert(
            kv.0.as_str().to_owned(),
            kv.1.to_str().unwrap_or("").to_owned(),
        );
    });
    Ok(HttpResponse {
        status: body.status().as_u16(),
        headers: resp_headers,
        data: body.bytes().await?,
    })
}
